﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.SdkTypeProxy.Metadata;
using Microsoft.Crm.Sdk.Metadata;

using Custom.Crm.General.Utility;

namespace Custom.Crm.General
{
    public class Mapper : IPlugin
    {
        /// <summary>The XML defining the field mappings</summary>
        private XmlDocument m_mapperDoc = null;

        private PropertyUtility m_propertyUtil = null;

        /// <summary>
        /// Constructor for the class - recieves two variables when the workflow engine.
        /// </summary>
        /// <param name="unSecure">Unsecure data entered while registering the plugin.
        /// Example of unSecure configuration:  (See Mapper.xml)
        /// </param>
        /// <param name="Secure">Secure data entered while registering the plugin</param>
        public Mapper(string unSecure, string Secure)
        {
            if (unSecure != null && unSecure.Length > 0)
            {
                try
                {
                    // Get the Configurations from the unSecure properties.
                    m_mapperDoc = new XmlDocument();
                    m_mapperDoc.LoadXml(unSecure);
                }
                catch (XmlException)
                {
                    throw new Exception("Invalid Mapping XML");
                }
            }
        }

        public void Execute(IPluginExecutionContext context)
        {
            if (m_mapperDoc != null)
            {
                // Set a reference to the Property Utility under the current context
                m_propertyUtil = new Utility.PropertyUtility(context);

                foreach (XmlNode mapNode in m_mapperDoc.DocumentElement.ChildNodes)
                {
                    // Get the trigger field from the XML
                    string triggerFieldName = mapNode.Attributes["fieldname"].Value;
                    string triggerFieldValue = m_propertyUtil.GetAttributeValue(triggerFieldName);

                    // Get a reference to the target fields
                    XmlNode targetGroupNode = mapNode.SelectSingleNode("target");

                    // If there is no value for the trigger field, then we must set one or exit.
                    if (triggerFieldValue == null || 
                        (triggerFieldValue == "0" && (mapNode.Attributes["zeroisnull"] != null && mapNode.Attributes["zeroisnull"].Value == "true")))
                    {
                        // Only continue processing if instructed to default the trigger field by the order of the source values.
                        if (mapNode.Attributes["defaultifnull"] == null || mapNode.Attributes["defaultifnull"].Value != "true")
                            break;

                        triggerFieldValue = SetTriggerValueBySourceOrder(triggerFieldName, mapNode.SelectSingleNode("sources"));

                        // If a non-null source can be determined, then stop processing this map.
                        if (triggerFieldValue == null)
                            break;
                    }

                    // Iterate through the target element collection.
                    if (targetGroupNode != null)
                    {
                        foreach (XmlNode targetFieldNode in targetGroupNode.ChildNodes)
                        {
                            try
                            {
                                // Get target field details
                                string mapName = targetFieldNode.Attributes["mapname"].Value;
                                string targetField = targetFieldNode.Attributes["fieldname"].Value;

                                // Ensure we have the mapname and target field name before continuing.
                                if (mapName != null & targetField != null)
                                {
                                    // Determine the source field by the trigger field's value.
                                    string sourceField = null;
                                    XmlNode sourceFieldNode = mapNode.SelectSingleNode("sources/source[@value='" + triggerFieldValue + "']/field[@mapname='" + mapName + "']");

                                    // Ensure the source field is not null and has the appropriate attributes.
                                    // Get the fieldname value.
                                    if (sourceFieldNode != null && sourceFieldNode.Attributes["fieldname"] != null)
                                        sourceField = sourceFieldNode.Attributes["fieldname"].Value;

                                    // Process the field copy.
                                    if (sourceField != null)
                                    {
                                        m_propertyUtil.CopyAttributeValue(sourceField, targetField);
                                    }
                                }
                            }
                            catch
                            {
                                // IGNORE: Don't stop processing due to exception.
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Iterate through all source groups by order to find a non-null collection and set a default trigger value.
        /// </summary>
        /// <param name="triggerFieldName">Trigger Field Name</param>
        /// <param name="sources">XML Node parenting the Source Field Collection</param>
        /// <returns>Trigger field's new default value</returns>
        public string SetTriggerValueBySourceOrder(string triggerFieldName, XmlNode sources)
        {
            // Iterate through all source groups by order
            foreach (XmlNode sourceNode in sources.ChildNodes)
            {
                string triggerValue = null;

                // Get the current source records' trigger value
                if (sourceNode.Attributes["value"] != null)
                    triggerValue = sourceNode.Attributes["value"].Value;

                // Iterate through the field collection to determine if null or not.
                foreach (XmlNode sourceFieldNode in sourceNode.ChildNodes)
                {
                    if (sourceFieldNode.Attributes["fieldname"] != null && sourceFieldNode.Attributes["fieldname"].Value != null)
                    {
                        // Get the source field value.
                        string fieldValue = m_propertyUtil.GetAttributeValue(sourceFieldNode.Attributes["fieldname"].Value);

                        // If a value is present, then default to this source collection.
                        if (fieldValue != null && fieldValue.Length > 0)
                        {
                            /// TODO: Could put a lookup to the metadata to determine how to handle non-picklists.  The rest of the code is non-picklist compliant.

                            m_propertyUtil.SetAttributeValueOnTarget(triggerFieldName, new Picklist(int.Parse(triggerValue)));
                            return triggerValue;
                        }
                    }
                }
            }

            return null;
        }
    }
}
